package com.piece.core.resource.config;

import com.google.common.collect.Multimap;
import com.piece.core.framework.constant.ExceptionConstants;
import com.piece.core.framework.support.response.AjaxResponse;
import com.piece.core.framework.util.basic.I18nUtil;
import com.piece.core.framework.util.string.JsonUtil;
import com.piece.core.web.authority.AccessDecisionManagerCustomizer;
import com.piece.core.web.authority.AuthorityFactory;
import com.piece.core.web.properties.SecurityProperties;
import com.piece.core.web.util.ResponseUtil;
import com.piece.core.web.util.ServletUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.authentication.TokenExtractor;
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
import org.springframework.security.oauth2.provider.expression.OAuth2WebSecurityExpressionHandler;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.web.cors.CorsUtils;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Import(CustomResourceServerHandlerConfig.class)
public class CustomResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private ApplicationContext applicationContext;

    @Resource
    private SecurityProperties securityProperties;

    @Resource
    private AuthorityFactory authorityFactory;

    @Resource
    private TokenExtractor tokenExtractor;

    @Autowired(required = false)
    private AccessDecisionManagerCustomizer accessDecisionManagerCustomizer;

    @Resource
    @Qualifier("resourceServerTokenServices")
    private ResourceServerTokenServices resourceServerTokenServices;

    public OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler() {
        OAuth2WebSecurityExpressionHandler expressionHandler = new OAuth2WebSecurityExpressionHandler();
        expressionHandler.setApplicationContext(applicationContext);
        return expressionHandler;
    }

    public OAuth2AccessDeniedHandler oAuth2AccessDeniedHandler() {
        return new OAuth2AccessDeniedHandler() {

            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException {
                ResponseUtil.responseFailed(response, I18nUtil.message(ExceptionConstants.ACCESS));
            }
        };
    }

    public AuthenticationEntryPoint authenticationEntryPoint() {
        return new AuthenticationEntryPoint() {
            private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

            @Override
            public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
//                if (ServletUtil.isAjaxRequest(request)) {
//                    ServletUtil.renderString(response, JsonUtil.toJson(AjaxResponse.error(e.getMessage())));
//                } else {
//                    redirectStrategy.sendRedirect(request, response, securityProperties.getProcess().getLoginUrl());
//                }
                ServletUtil.renderString(response, JsonUtil.toJson(AjaxResponse.error(e.getMessage())));
            }
        };
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenExtractor(tokenExtractor)
                .stateless(true)
                .tokenServices(resourceServerTokenServices)
                .expressionHandler(oAuth2WebSecurityExpressionHandler())
                .accessDeniedHandler(oAuth2AccessDeniedHandler())
                .authenticationEntryPoint(authenticationEntryPoint());
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // 鉴权相关的配置
        Multimap<HttpMethod, String> permits = authorityFactory.permitAllMethods(applicationContext, securityProperties);

        http.headers().frameOptions().disable()
                .and().authorizeRequests()
                .antMatchers(HttpMethod.GET, permits.get(HttpMethod.GET).toArray(new String[0]))
                .permitAll()
                .antMatchers(HttpMethod.POST, permits.get(HttpMethod.POST).toArray(new String[0]))
                .permitAll()
                .antMatchers(HttpMethod.PUT, permits.get(HttpMethod.PUT).toArray(new String[0]))
                .permitAll()
                .antMatchers(HttpMethod.DELETE, permits.get(HttpMethod.DELETE).toArray(new String[0]))
                .permitAll()
                .requestMatchers(CorsUtils::isPreFlightRequest)
                .permitAll()
                .anyRequest().authenticated();

        if (null != accessDecisionManagerCustomizer) {
            http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(accessDecisionManagerCustomizer);
                        return object;
                    }
                });
        }

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().csrf().disable();;
    }

    /**
     * 重写扩展功能
     */
    public HttpSecurity setHttp(HttpSecurity http) {
        return http;
    }
}
